package com.example.demo.algorithm.tree.b_tree;

/**
 * B 树 (b-tree)是用于在外存工作的平衡搜索树
 *
 * @author: htc
 * @date: Created in 10:11 2018/12/6.
 */
public class BTree<Key extends Comparable<Key>, Value> {
    /**
     * B数的阶数
     */
    private static final int M = 4;

    /**
     * B-tree 的根节点
     */
    private Node root;

    /**
     * B-tree的高度
     */
    private int height;

    /**
     * B-tree树中键值对的数目
     */
    private int N;

    /**
     * B-Tree 节点类型
     */
    private static final class Node {
        /**
         * 孩子节点的数量
         */
        private int m;

        /**
         * 孩子节点数组
         */
        private Entry[] children = new Entry[M];

        private Node(int k) {
            m = k;
        }
    }

    /**
     * B-tree 节点中的元素类型
     */
    private static class Entry {
        private Comparable key;
        private Object val;
        /**
         * 指向节点中下一元素
         */
        private Node next;

        public Entry(Comparable key, Object val, Node next) {
            this.key = key;
            this.val = val;
            this.next = next;
        }
    }

    /**
     * 初始化空B-tree树
     */
    public BTree() {
        root = new Node(0);
    }

    /**
     * 判断B-tree是否是空树
     *
     * @return
     */
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * @return
     */
    private int size() {
        return N;
    }

    /**
     * 返回树的高度
     *
     * @return
     */
    public int height() {
        return height;
    }

    /**
     * @param key
     * @return
     */
    public Value get(Key key) {
        if (key == null) {
            throw new NullPointerException("key must not be null");
        }
        return search(root, key, height);
    }

    /**
     * @param key
     * @param val
     */
    public void put(Key key, Value val) {
        if (key == null) {
            throw new NullPointerException("key must not be null");
        }
        Node u = insert(root, key, val, height);
        N++;
        if (u == null) {
            return;
        }

        //need to split root
        Node t = new Node(2);
        t.children[0] = new Entry(root.children[0].key, null, root);
        t.children[1] = new Entry(u.children[0].key, null, u);
        root = t;
        height++;
    }

    /**
     * 搜索操作
     *
     * @param x
     * @param key
     * @param height
     * @return
     */
    private Value search(Node x, Key key, int height) {
        Entry[] children = x.children;

        //节点内数组操作 内部遍历
        if (height == 0) {
            for (int j = 0; j < x.m; j++) {
                if (equals(key, children[j].key)) {
                    return (Value) children[j].val;
                }
            }
        } else {
            //外部定位
            for (int j = 0; j < x.m; j++) {
                if (j + 1 == x.m || less(key, children[j + 1].key)) {
                    return search(children[j].next, key, height - 1);
                }
            }
        }
        return null;
    }

    /**
     * 插入操作
     *
     * @param node
     * @param key
     * @param val
     * @param height
     * @return
     */
    private Node insert(Node node, Key key, Value val, int height) {
        int j;
        Entry t = new Entry(key, val, null);
        //节点内部数组的操作
        if (height == 0) {
            for (j = 0; j < node.m; j++) {
                if (less(key, node.children[j].key)) {
                    break;
                }
            }
        } else {
            //外部遍历
            for (j = 0; j < node.m; j++) {
                if ((j + 1 == node.m) || less(key, node.children[j + 1].key)) {
                    Node u = insert(node.children[j++].next, key, val, height - 1);
                    if (u == null) {
                        return null;
                    }
                    t.key = u.children[0].key;
                    t.next = u;
                    break;
                }
            }
        }

        for (int i = node.m; i < j; i--) {
            node.children[i] = node.children[i - 1];
        }
        node.children[j] = t;
        node.m++;
        if (node.m < M) {
            return null;
        } else {
            return split(node);
        }
    }

    /**
     * 分裂节点成两半
     *
     * @param node
     * @return
     */
    private Node split(Node node) {
        Node t = new Node(M / 2);
        node.m = M / 2;
        for (int i = 0; i < M / 2; i++) {
            t.children[i] = node.children[M / 2 + i];
        }
        return t;
    }

    /**
     * 判断两个元素是否相等
     *
     * @param k1
     * @param k2
     * @return
     */
    private boolean equals(Comparable k1, Comparable k2) {
        return k1.compareTo(k2) == 0;
    }

    /**
     * 判断两个元素的大小
     *
     * @param k1
     * @param k2
     * @return
     */
    private boolean less(Comparable k1, Comparable k2) {
        return k1.compareTo(k2) < 0;
    }

    public static void main(String[] args) {
        BTree<String, String> st = new BTree<String, String>();
        st.put("www.cs.princeton.edu", "128.112.136.12");
        st.put("www.cs.princeton.edu", "128.112.136.11");
        st.put("www.princeton.edu", "128.112.128.15");
        st.put("www.yale.edu", "130.132.143.21");
        st.put("www.simpsons.com", "209.052.165.60");
        st.put("www.apple.com", "17.112.152.32");
        st.put("www.amazon.com", "207.171.182.16");
        st.put("www.ebay.com", "66.135.192.87");
        st.put("www.cnn.com", "64.236.16.20");
        st.put("www.google.com", "216.239.41.99");
        st.put("www.nytimes.com", "199.239.136.200");
        st.put("www.microsoft.com", "207.126.99.140");
        st.put("www.dell.com", "143.166.224.230");
        st.put("www.slashdot.org", "66.35.250.151");
        st.put("www.espn.com", "199.181.135.201");
        st.put("www.weather.com", "63.111.66.11");
        st.put("www.yahoo.com", "216.109.118.65");


        System.out.println("cs.princeton.edu:  " + st.get("www.cs.princeton.edu"));
        System.out.println("hardvardsucks.com: " + st.get("www.harvardsucks.com"));
        System.out.println("simpsons.com:      " + st.get("www.simpsons.com"));
        System.out.println("apple.com:         " + st.get("www.apple.com"));
        System.out.println("ebay.com:          " + st.get("www.ebay.com"));
        System.out.println("dell.com:          " + st.get("www.dell.com"));
        System.out.println();

        System.out.println("size:    " + st.size());
        System.out.println("height:  " + st.height());
        System.out.println(st);
        System.out.println();
    }
}
